#include "collection_mongotest.hpp"

USERVER_NAMESPACE_BEGIN

namespace bson = formats::bson;
namespace mongo = storages::mongo;

namespace {
class Collection : public MongoPoolFixture {};
}  // namespace

UTEST_F(Collection, CollectionName) {
    static const std::string kCollectionName = "collection_name_test";
    auto coll = GetDefaultPool().GetCollection(kCollectionName);
    EXPECT_EQ(kCollectionName, coll.GetCollectionName());
}

UTEST_F(Collection, GetaddrinfoResolver) {
    clients::dns::Resolver* dns_resolver = nullptr;
    auto pool = MakePool({}, {}, dns_resolver);
    static const auto kFilter = bson::MakeDoc("x", 1);

    auto coll = pool.GetCollection("getaddrinfo");

    EXPECT_EQ(0, coll.CountApprox());
    EXPECT_EQ(0, coll.Count({}));
    EXPECT_EQ(0, coll.Count(kFilter));
}

UTEST_F(Collection, Read) {
    static const auto kFilter = bson::MakeDoc("x", 1);

    auto coll = GetDefaultPool().GetCollection("read");

    EXPECT_EQ(0, coll.CountApprox());
    EXPECT_EQ(0, coll.Count({}));
    EXPECT_EQ(0, coll.Count(kFilter));

    coll.InsertOne(bson::MakeDoc("x", 2));
    EXPECT_EQ(1, coll.CountApprox());
    EXPECT_EQ(1, coll.Count({}));
    EXPECT_EQ(0, coll.Count(kFilter));

    coll.InsertOne(kFilter);
    EXPECT_EQ(2, coll.CountApprox());
    EXPECT_EQ(2, coll.Count({}));
    EXPECT_EQ(1, coll.Count(kFilter));

    coll.InsertOne(bson::MakeDoc("x", 3));
    EXPECT_EQ(3, coll.CountApprox());
    EXPECT_EQ(3, coll.Count({}));
    EXPECT_EQ(1, coll.Count(kFilter));
    EXPECT_EQ(2, coll.Count(bson::MakeDoc("x", bson::MakeDoc("$gt", 1))));

    coll.InsertOne(kFilter);
    EXPECT_EQ(4, coll.CountApprox());
    EXPECT_EQ(4, coll.Count({}));
    EXPECT_EQ(2, coll.Count(kFilter));
    EXPECT_EQ(2, coll.Count(bson::MakeDoc("x", bson::MakeDoc("$gt", 1))));

    auto other_coll = GetDefaultPool().GetCollection("read_other");
    EXPECT_EQ(0, other_coll.CountApprox());
    EXPECT_EQ(0, other_coll.Count({}));
    EXPECT_EQ(0, other_coll.Count(kFilter));

    {
        size_t sum = 0;
        size_t count = 0;
        for (const auto& doc : coll.Find({})) {
            sum += doc["x"].As<size_t>();
            ++count;
        }
        EXPECT_EQ(4, count);
        EXPECT_EQ(7, sum);
    }
    {
        auto cursor = coll.Aggregate(MakeArray(bson::MakeDoc(
            "$group",
            bson::MakeDoc("_id", nullptr, "count", bson::MakeDoc("$sum", 1), "sum", bson::MakeDoc("$sum", "$x"))
        )));
        auto doc = *cursor.begin();
        EXPECT_EQ(++cursor.begin(), cursor.end());
        EXPECT_EQ(4, doc["count"].As<int>());
        EXPECT_EQ(7, doc["sum"].As<int>());
    }
    {
        bson::Document prev;
        for (const auto& doc : coll.Find(kFilter)) {
            EXPECT_EQ(1, doc["x"].As<int>());
            if (prev.HasMember("_id")) {
                EXPECT_NE(prev["_id"].As<bson::Oid>(), doc["_id"].As<bson::Oid>());
                EXPECT_NE(prev["_id"], doc["_id"]);
            }
            prev = doc;
        }
    }
    {
        auto cursor = coll.Find({});
        EXPECT_EQ(cursor.GetBatchSize(), 0) << "Default value should be 0 (use server side size)";
        EXPECT_NO_THROW(cursor.SetBatchSize(0)) << "Setting to 0 should be fine";
        EXPECT_TRUE(cursor);
        EXPECT_TRUE(cursor.HasMore());
        for ([[maybe_unused]] const auto& doc : cursor)
            ;  // exhaust
        EXPECT_FALSE(cursor);
        EXPECT_FALSE(cursor.HasMore());
        for ([[maybe_unused]] const auto& doc : cursor) {
            ADD_FAILURE() << "read from exhausted cursor succeeded";
        }
    }
    {
        auto cursor = coll.Find({});
        EXPECT_NO_THROW(cursor.SetBatchSize(1));
        EXPECT_EQ(cursor.GetBatchSize(), 1);

        EXPECT_TRUE(cursor);
        EXPECT_TRUE(cursor.HasMore());
        for ([[maybe_unused]] const auto& doc : cursor)
            ;  // exhaust
        EXPECT_FALSE(cursor);
        EXPECT_FALSE(cursor.HasMore());
        for ([[maybe_unused]] const auto& doc : cursor) {
            ADD_FAILURE() << "read from exhausted cursor succeeded";
        }
    }
    {
        auto cursor = coll.Find({});
        EXPECT_NO_THROW(cursor.SetBatchSize(1024));
        EXPECT_EQ(cursor.GetBatchSize(), 1024);

        EXPECT_TRUE(cursor);
        EXPECT_TRUE(cursor.HasMore());
        for ([[maybe_unused]] const auto& doc : cursor)
            ;  // exhaust
        EXPECT_FALSE(cursor);
        EXPECT_FALSE(cursor.HasMore());
        for ([[maybe_unused]] const auto& doc : cursor) {
            ADD_FAILURE() << "read from exhausted cursor succeeded";
        }
    }

    EXPECT_EQ(4, coll.CountApprox());
    EXPECT_EQ(0, other_coll.CountApprox());
}

UTEST_F(Collection, InsertOne) {
    auto coll = GetDefaultPool().GetCollection("insert_one");

    {
        auto result = coll.InsertOne(bson::MakeDoc("_id", 1));
        EXPECT_EQ(1, result.InsertedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    UEXPECT_THROW(coll.InsertOne(bson::MakeDoc("_id", 1)), mongo::DuplicateKeyException);
    {
        auto result = coll.InsertOne(bson::MakeDoc("_id", 1), mongo::options::SuppressServerExceptions{});
        EXPECT_EQ(0, result.InsertedCount());
        EXPECT_TRUE(result.WriteConcernErrors().empty());

        auto errors = result.ServerErrors();
        ASSERT_EQ(1, errors.size());
        EXPECT_EQ(11000, errors[0].Code());
        EXPECT_TRUE(errors[0].IsServerError());
        UEXPECT_THROW(errors[0].Throw({}), mongo::DuplicateKeyException);
    }
}

UTEST_F(Collection, InsertMany) {
    auto coll = GetDefaultPool().GetCollection("insert_many");

    {
        auto result = coll.InsertMany({});
        EXPECT_EQ(0, result.InsertedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.InsertMany({bson::MakeDoc("_id", 1)});
        EXPECT_EQ(1, result.InsertedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    UEXPECT_THROW(coll.InsertMany({bson::MakeDoc("_id", 2), bson::MakeDoc("_id", 1)}), mongo::DuplicateKeyException);
    {
        auto result = coll.InsertMany(
            {bson::MakeDoc("_id", 3), bson::MakeDoc("_id", 2), bson::MakeDoc("_id", 1)},
            mongo::options::SuppressServerExceptions{}
        );
        EXPECT_EQ(1, result.InsertedCount());
        EXPECT_TRUE(result.WriteConcernErrors().empty());

        auto errors = result.ServerErrors();
        ASSERT_EQ(1, errors.size());
        EXPECT_TRUE(errors[1].IsServerError());
        EXPECT_EQ(11000, errors[1].Code());
    }
}

UTEST_F(Collection, ReplaceOne) {
    auto coll = GetDefaultPool().GetCollection("replace_one");

    coll.InsertOne(bson::MakeDoc("_id", 1));
    UEXPECT_THROW(
        coll.ReplaceOne(bson::MakeDoc("_id", 1), bson::MakeDoc("$set", bson::MakeDoc("x", 1))),
        mongo::InvalidQueryArgumentException
    );
    {
        auto result = coll.ReplaceOne(bson::MakeDoc("_id", 1), bson::MakeDoc("x", 1));
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.ReplaceOne(bson::MakeDoc("_id", 2), bson::MakeDoc("x", 2));
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.ReplaceOne(bson::MakeDoc("_id", 2), bson::MakeDoc("x", 2), mongo::options::Upsert{});
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(1, result.UpsertedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        auto upserted_ids = result.UpsertedIds();
        ASSERT_TRUE(upserted_ids[0].IsInt32());
        EXPECT_EQ(2, upserted_ids[0].As<int>());
    }
    {
        auto result = coll.ReplaceOne(bson::MakeDoc(), bson::MakeDoc("x", 3));
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    EXPECT_EQ(2, coll.CountApprox());
}

UTEST_F(Collection, Update) {
    auto coll = GetDefaultPool().GetCollection("update");

    coll.InsertOne(bson::MakeDoc("_id", 1));
    UEXPECT_THROW(coll.UpdateOne(bson::MakeDoc("_id", 1), bson::MakeDoc("x", 1)), mongo::InvalidQueryArgumentException);
    UEXPECT_THROW(
        coll.UpdateMany(bson::MakeDoc("_id", 1), bson::MakeDoc("x", 1)), mongo::InvalidQueryArgumentException
    );

    UpdateOneDoc(coll);

    {
        auto result = coll.UpdateOne(bson::MakeDoc("_id", 1), bson::MakeDoc("$set", bson::MakeDoc("x", 10)));
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.UpdateOne(bson::MakeDoc("_id", 2), bson::MakeDoc("$set", bson::MakeDoc("x", 20)));
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.UpdateOne(
            bson::MakeDoc("_id", 2), bson::MakeDoc("$set", bson::MakeDoc("x", 20)), mongo::options::Upsert{}
        );
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(1, result.UpsertedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        auto upserted_ids = result.UpsertedIds();
        ASSERT_TRUE(upserted_ids[0].IsInt32());
        EXPECT_EQ(2, upserted_ids[0].As<int>());
    }
    {
        auto result = coll.UpdateMany(bson::MakeDoc(), bson::MakeDoc("$set", bson::MakeDoc("x", 20)));
        EXPECT_EQ(2, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.UpdateMany(
            bson::MakeDoc("_id", 3), bson::MakeDoc("$set", bson::MakeDoc("x", 30)), mongo::options::Upsert{}
        );
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(1, result.UpsertedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        auto upserted_ids = result.UpsertedIds();
        ASSERT_TRUE(upserted_ids[0].IsInt32());
        EXPECT_EQ(3, upserted_ids[0].As<int>());
    }
    {
        auto result = coll.UpdateOne(bson::MakeDoc(), bson::MakeDoc("$set", bson::MakeDoc("x", 40)));
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    EXPECT_EQ(3, coll.CountApprox());
}

UTEST_F(Collection, Delete) {
    auto coll = GetDefaultPool().GetCollection("delete");

    {
        std::vector<formats::bson::Document> docs;
        docs.reserve(10);
        for (int i = 0; i < 10; ++i) docs.push_back(bson::MakeDoc("x", i));
        coll.InsertMany(std::move(docs));
    }

    {
        auto result = coll.DeleteOne(bson::MakeDoc("x", 1));
        EXPECT_EQ(1, result.DeletedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        EXPECT_EQ(0, coll.Count(bson::MakeDoc("x", 1)));
    }
    {
        auto result = coll.DeleteOne(bson::MakeDoc("x", bson::MakeDoc("$gt", 6)));
        EXPECT_EQ(1, result.DeletedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        EXPECT_EQ(2, coll.Count(bson::MakeDoc("x", bson::MakeDoc("$gt", 6))));
    }
    {
        auto result = coll.DeleteMany(bson::MakeDoc("x", bson::MakeDoc("$gt", 10)));
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.DeleteMany(bson::MakeDoc("x", bson::MakeDoc("$lt", 5)));
        EXPECT_EQ(4, result.DeletedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    EXPECT_EQ(4, coll.CountApprox());
}

UTEST_F(Collection, FindAndModify) {
    auto coll = GetDefaultPool().GetCollection("find_and_modify");

    coll.InsertOne(bson::MakeDoc("_id", 1, "x", 10));

    {
        auto result = coll.FindAndModify(bson::MakeDoc("_id", 2), bson::MakeDoc("x", 20));
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_FALSE(result.FoundDocument());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
    }
    {
        auto result = coll.FindAndModify(bson::MakeDoc("_id", 2), bson::MakeDoc("x", 20), mongo::options::Upsert{});
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(1, result.UpsertedCount());
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_FALSE(result.FoundDocument());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        auto upserted_ids = result.UpsertedIds();
        ASSERT_TRUE(upserted_ids[0].IsInt32());
        EXPECT_EQ(2, upserted_ids[0].As<int>());
    }
    {
        auto result = coll.FindAndModify(
            bson::MakeDoc("_id", 3), bson::MakeDoc("x", 30), mongo::options::Upsert{}, mongo::options::ReturnNew{}
        );
        EXPECT_EQ(0, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(1, result.UpsertedCount());
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        auto upserted_ids = result.UpsertedIds();
        ASSERT_TRUE(upserted_ids[0].IsInt32());
        EXPECT_EQ(3, upserted_ids[0].As<int>());
        ASSERT_TRUE(result.FoundDocument());
        auto doc = *result.FoundDocument();
        EXPECT_EQ(doc["_id"], upserted_ids[0]);
        EXPECT_EQ(30, doc["x"].As<int>());
    }
    {
        auto result = coll.FindAndModify(bson::MakeDoc("_id", 1), bson::MakeDoc("$inc", bson::MakeDoc("x", 2)));
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        ASSERT_TRUE(result.FoundDocument());
        auto doc = *result.FoundDocument();
        EXPECT_EQ(1, doc["_id"].As<int>());
        EXPECT_EQ(10, doc["x"].As<int>());

        EXPECT_EQ(1, coll.Count(bson::MakeDoc("x", 12)));
    }
    {
        auto result = coll.FindAndModify(
            bson::MakeDoc("_id", 1), bson::MakeDoc("$inc", bson::MakeDoc("x", 2)), mongo::options::ReturnNew{}
        );
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        ASSERT_TRUE(result.FoundDocument());
        auto doc = *result.FoundDocument();
        EXPECT_EQ(1, doc["_id"].As<int>());
        EXPECT_EQ(14, doc["x"].As<int>());
    }
    {
        auto result = coll.FindAndModify(
            bson::MakeDoc(),
            bson::MakeDoc("$inc", bson::MakeDoc("x", -1)),
            mongo::options::Sort{{"x", mongo::options::Sort::kDescending}}
        );
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        ASSERT_TRUE(result.FoundDocument());
        auto doc = *result.FoundDocument();
        EXPECT_EQ(3, doc["_id"].As<int>());
        EXPECT_EQ(30, doc["x"].As<int>());
    }
    {
        auto result = coll.FindAndModify(
            bson::MakeDoc(),
            bson::MakeDoc("$inc", bson::MakeDoc("x", -1)),
            mongo::options::Sort{{"x", mongo::options::Sort::kAscending}},
            mongo::options::Projection{"y"}
        );
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(1, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_EQ(0, result.DeletedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        ASSERT_TRUE(result.FoundDocument());
        auto doc = *result.FoundDocument();
        EXPECT_EQ(1, doc["_id"].As<int>());
        EXPECT_FALSE(doc.HasMember("x"));
    }
    {
        auto result = coll.FindAndRemove(
            bson::MakeDoc(),
            mongo::options::Sort{{"x", mongo::options::Sort::kAscending}},
            mongo::options::Projection{"y"}
        );
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_EQ(1, result.DeletedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        ASSERT_TRUE(result.FoundDocument());
        auto doc = *result.FoundDocument();
        EXPECT_EQ(1, doc["_id"].As<int>());
        EXPECT_FALSE(doc.HasMember("x"));
    }
    {
        auto result = coll.FindAndRemove(
            bson::MakeDoc(),
            mongo::options::Sort{{"x", mongo::options::Sort::kDescending}},
            mongo::options::Projection{}.Exclude("_id")
        );
        EXPECT_EQ(1, result.MatchedCount());
        EXPECT_EQ(0, result.ModifiedCount());
        EXPECT_EQ(0, result.UpsertedCount());
        EXPECT_EQ(1, result.DeletedCount());
        EXPECT_TRUE(result.UpsertedIds().empty());
        EXPECT_TRUE(result.ServerErrors().empty());
        EXPECT_TRUE(result.WriteConcernErrors().empty());
        ASSERT_TRUE(result.FoundDocument());
        auto doc = *result.FoundDocument();
        EXPECT_FALSE(doc.HasMember("_id"));
        EXPECT_EQ(29, doc["x"].As<int>());
    }
    coll.FindAndModify(
        bson::MakeDoc("_id", 1),
        bson::MakeDoc(),
        mongo::options::Upsert{},
        mongo::options::WriteConcern::kUnacknowledged
    );
    coll.FindAndRemove(
        bson::MakeDoc(),
        mongo::options::Sort{{"_id", mongo::options::Sort::kDescending}},
        mongo::options::WriteConcern::kUnacknowledged
    );
    EXPECT_EQ(1, coll.CountApprox());
    EXPECT_EQ(1, coll.Count(bson::MakeDoc("_id", 1)));
}

UTEST_F(Collection, AggregateOut) { SampleMongoPool(GetDefaultPool()); }

UTEST_F(Collection, Drop) {
    const std::string collection_name = "drop";
    auto& pool = GetDefaultPool();
    auto coll = pool.GetCollection(collection_name);

    {
        coll.InsertOne(bson::MakeDoc("x", 1));
        EXPECT_EQ(1, coll.Count({}));
        UEXPECT_NO_THROW(coll.Drop());
        EXPECT_FALSE(pool.HasCollection(collection_name));
        EXPECT_EQ(0, coll.Count({}));
    }

    {
        coll.InsertOne(bson::MakeDoc("x", 1));
        EXPECT_EQ(1, coll.Count({}));
        UEXPECT_NO_THROW(coll.Drop());
        EXPECT_FALSE(pool.HasCollection(collection_name));
        EXPECT_EQ(0, coll.Count({}));
    }

    {
        coll.InsertOne(bson::MakeDoc("x", 1));
        EXPECT_EQ(1, coll.Count({}));
        UEXPECT_NO_THROW(coll.Drop());
        EXPECT_FALSE(pool.HasCollection(collection_name));
        EXPECT_EQ(0, coll.Count({}));
    }

    {
        coll.InsertOne(bson::MakeDoc("x", 1));
        UEXPECT_NO_THROW(coll.Drop(mongo::options::WriteConcern::Level::kMajority));
        EXPECT_FALSE(pool.HasCollection(collection_name));
    }

    {
        coll.InsertOne(bson::MakeDoc("x", 1));
        UEXPECT_NO_THROW(coll.Drop(mongo::options::WriteConcern::Level::kUnacknowledged));
        EXPECT_FALSE(pool.HasCollection(collection_name));
    }
}

UTEST_F(Collection, LargeDocRoundtrip) {
    auto coll = GetDefaultPool().GetCollection("large_doc");

    std::string large_string(12 * 1024 * 1024, '\0');
    for (size_t i = 0; i < large_string.size(); ++i) {
        large_string[i] = i % 128;  // Must be UTF-8, ASCII suffices
    }

    coll.InsertOne(bson::MakeDoc("s", large_string));
    auto result = coll.FindOne({});
    ASSERT_TRUE(result);
    EXPECT_EQ(large_string, (*result)["s"].As<std::string>());
}

UTEST_F(Collection, ExecuteOps) {
    auto mongo_coll = GetDefaultPool().GetCollection("execute_ops");

    mongo_coll.InsertMany({
        formats::bson::MakeDoc("_id", 1, "score", 0, "name", "some_name_0"),
        formats::bson::MakeDoc("_id", 2, "score", 1, "name", "some_name_1"),
        formats::bson::MakeDoc("_id", 3, "score", 2),
        formats::bson::MakeDoc("_id", 4, "score", 3, "name", "some_name_3"),
    });

    EXPECT_EQ(GetWinners(mongo_coll), "some_name_3 <anonymous> some_name_1 ");
}

std::vector<formats::bson::Value> SortBsonValues(std::vector<formats::bson::Value> values) {
    std::sort(values.begin(), values.end(), [](const auto& a, const auto& b) {
        if (a.IsString() && b.IsString()) {
            return a.template As<std::string>() < b.template As<std::string>();
        }
        if (a.IsInt32() && b.IsInt32()) {
            return a.template As<int32_t>() < b.template As<int32_t>();
        }
        if (a.IsInt64() && b.IsInt64()) {
            return a.template As<int64_t>() < b.template As<int64_t>();
        }
        if (a.IsDouble() && b.IsDouble()) {
            return a.template As<double>() < b.template As<double>();
        }

        return a.template ConvertTo<std::string>() < b.template ConvertTo<std::string>();
    });

    return values;
}

UTEST_F(Collection, Distinct) {
    {
        auto coll = GetDefaultPool().GetCollection("distinct");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "category", "A", "value", 10),
             bson::MakeDoc("_id", 2, "category", "B", "value", 20),
             bson::MakeDoc("_id", 3, "category", "A", "value", 30),
             bson::MakeDoc("_id", 4, "category", "C", "value", 40),
             bson::MakeDoc("_id", 5, "category", "B", "value", 50),
             bson::MakeDoc("_id", 6, "category", "A", "value", 60)}
        );

        {
            const auto sorted_result = SortBsonValues(coll.Distinct("category"));
            ASSERT_EQ(3, sorted_result.size());
            EXPECT_EQ("A", sorted_result[0].As<std::string>());
            EXPECT_EQ("B", sorted_result[1].As<std::string>());
            EXPECT_EQ("C", sorted_result[2].As<std::string>());
        }

        {
            const auto sorted_result = SortBsonValues(coll.Distinct("value"));
            ASSERT_EQ(6, sorted_result.size());
            EXPECT_EQ(10, sorted_result[0].As<int32_t>());
            EXPECT_EQ(20, sorted_result[1].As<int32_t>());
            EXPECT_EQ(30, sorted_result[2].As<int32_t>());
            EXPECT_EQ(40, sorted_result[3].As<int32_t>());
            EXPECT_EQ(50, sorted_result[4].As<int32_t>());
            EXPECT_EQ(60, sorted_result[5].As<int32_t>());
        }

        {
            const auto result = coll.Distinct("nonexistent");
            EXPECT_TRUE(result.empty());
        }
    }

    {
        auto coll = GetDefaultPool().GetCollection("distinct_filter");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "category", "A", "status", "active", "score", 85),
             bson::MakeDoc("_id", 2, "category", "B", "status", "inactive", "score", 70),
             bson::MakeDoc("_id", 3, "category", "A", "status", "active", "score", 90),
             bson::MakeDoc("_id", 4, "category", "C", "status", "active", "score", 60),
             bson::MakeDoc("_id", 5, "category", "B", "status", "active", "score", 95),
             bson::MakeDoc("_id", 6, "category", "A", "status", "inactive", "score", 75)}
        );

        {
            const auto sorted_result = SortBsonValues(coll.Distinct("category", bson::MakeDoc("status", "active")));
            ASSERT_EQ(3, sorted_result.size());
            EXPECT_EQ("A", sorted_result[0].As<std::string>());
            EXPECT_EQ("B", sorted_result[1].As<std::string>());
            EXPECT_EQ("C", sorted_result[2].As<std::string>());
        }

        {
            const auto sorted_result =
                SortBsonValues(coll.Distinct("category", bson::MakeDoc("score", bson::MakeDoc("$gte", 80))));

            ASSERT_EQ(2, sorted_result.size());
            EXPECT_EQ("A", sorted_result[0].As<std::string>());
            EXPECT_EQ("B", sorted_result[1].As<std::string>());
        }

        {
            const auto sorted_result = SortBsonValues(coll.Distinct(
                "category",
                bson::MakeDoc(
                    "$and",
                    bson::MakeArray(bson::MakeDoc("status", "active"), bson::MakeDoc("score", bson::MakeDoc("$gt", 80)))
                )
            ));
            ASSERT_EQ(2, sorted_result.size());
            EXPECT_EQ("A", sorted_result[0].As<std::string>());
            EXPECT_EQ("B", sorted_result[1].As<std::string>());
        }

        {
            const auto result = coll.Distinct("category", bson::MakeDoc("status", "unknown"));
            EXPECT_TRUE(result.empty());
        }
    }

    {
        auto coll = GetDefaultPool().GetCollection("distinct_mixed");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "mixed_field", "string_value"),
             bson::MakeDoc("_id", 2, "mixed_field", 42),
             bson::MakeDoc("_id", 3, "mixed_field", 3.14),
             bson::MakeDoc("_id", 4, "mixed_field", true),
             bson::MakeDoc("_id", 5, "mixed_field", "string_value"),
             bson::MakeDoc("_id", 6, "mixed_field", 42),
             bson::MakeDoc("_id", 7),
             bson::MakeDoc("_id", 8, "mixed_field", nullptr)}
        );

        const auto result = coll.Distinct("mixed_field");

        EXPECT_GE(result.size(), 4);

        bool has_string = false, has_int = false, has_double = false, has_bool = false;
        for (const auto& value : result) {
            if (value.IsString())
                has_string = true;
            else if (value.IsInt32())
                has_int = true;
            else if (value.IsDouble())
                has_double = true;
            else if (value.IsBool())
                has_bool = true;
        }

        EXPECT_TRUE(has_string);
        EXPECT_TRUE(has_int);
        EXPECT_TRUE(has_double);
        EXPECT_TRUE(has_bool);
    }

    {
        auto coll = GetDefaultPool().GetCollection("distinct_arrays");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "tags", bson::MakeArray("red", "blue")),
             bson::MakeDoc("_id", 2, "tags", bson::MakeArray("green", "blue")),
             bson::MakeDoc("_id", 3, "tags", bson::MakeArray("red", "yellow")),
             bson::MakeDoc("_id", 4, "tags", bson::MakeArray("blue", "purple")),
             bson::MakeDoc("_id", 5, "single_tag", "orange")}
        );

        const auto sorted_result = SortBsonValues(coll.Distinct("tags"));

        ASSERT_EQ(5, sorted_result.size());
        EXPECT_EQ("blue", sorted_result[0].As<std::string>());
        EXPECT_EQ("green", sorted_result[1].As<std::string>());
        EXPECT_EQ("purple", sorted_result[2].As<std::string>());
        EXPECT_EQ("red", sorted_result[3].As<std::string>());
        EXPECT_EQ("yellow", sorted_result[4].As<std::string>());

        coll = GetDefaultPool().GetCollection("distinct_collation");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "name", "apple"),
             bson::MakeDoc("_id", 2, "name", "Apple"),
             bson::MakeDoc("_id", 3, "name", "APPLE"),
             bson::MakeDoc("_id", 4, "name", "banana"),
             bson::MakeDoc("_id", 5, "name", "Banana")}
        );
        {
            const auto result = coll.Distinct("name");
            EXPECT_EQ(5, result.size());
        }

        {
            mongo::options::Collation collation("en");
            collation.SetStrength(mongo::options::Collation::Strength::kPrimary);

            const auto result = coll.Distinct("name", collation);
            EXPECT_EQ(2, result.size());
        }

        {
            mongo::options::Collation collation("en");
            collation.SetStrength(mongo::options::Collation::Strength::kSecondary);

            const auto result = coll.Distinct("name", collation);
            EXPECT_GE(result.size(), 2);
        }
    }

    {
        auto coll = GetDefaultPool().GetCollection("distinct_comment");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "type", "A"),
             bson::MakeDoc("_id", 2, "type", "B"),
             bson::MakeDoc("_id", 3, "type", "A")}
        );

        UEXPECT_NO_THROW({
            const auto result = coll.Distinct("type", mongo::options::Comment("test distinct operation"));
            EXPECT_EQ(2, result.size());
        });
    }

    {
        auto coll = GetDefaultPool().GetCollection("distinct_timeout");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "category", "X"),
             bson::MakeDoc("_id", 2, "category", "Y"),
             bson::MakeDoc("_id", 3, "category", "X")}
        );

        UEXPECT_NO_THROW({
            const auto result =
                coll.Distinct("category", mongo::options::MaxServerTime(std::chrono::milliseconds(5000)));
            EXPECT_EQ(2, result.size());
        });
    }

    {
        auto coll = GetDefaultPool().GetCollection("distinct_nested");

        coll.InsertMany(
            {bson::MakeDoc("_id", 1, "user", bson::MakeDoc("profile", bson::MakeDoc("role", "admin"))),
             bson::MakeDoc("_id", 2, "user", bson::MakeDoc("profile", bson::MakeDoc("role", "user"))),
             bson::MakeDoc("_id", 3, "user", bson::MakeDoc("profile", bson::MakeDoc("role", "admin"))),
             bson::MakeDoc("_id", 4, "user", bson::MakeDoc("profile", bson::MakeDoc("role", "guest")))}
        );

        const auto sorted_result = SortBsonValues(coll.Distinct("user.profile.role"));

        ASSERT_EQ(3, sorted_result.size());
        EXPECT_EQ("admin", sorted_result[0].As<std::string>());
        EXPECT_EQ("guest", sorted_result[1].As<std::string>());
        EXPECT_EQ("user", sorted_result[2].As<std::string>());
    }
}

USERVER_NAMESPACE_END
